package mandel;

public class ColorRamp {

	private int taillePalette;
	private int[] palette;
	private double decalageColor; // entre [0 et 1]
	private double factorColor;
	
	public static int WHITE = computeColor(255, 255, 255);
	public static int BLACK = computeColor(0,   0,   0);
	public static int GRAY = computeColor(125,   125,   125);
	public static int RED = computeColor(255,   0,   0);
	public static int RED_LIGHT = computeColor(255,   50,   50);
	public static int BLUE = computeColor(0,   0,   255);
	public static int BLUE_LIGHT = computeColor(50,   50,   255);
	public static int GREEN = computeColor(0,   255,   0);
	public static int GREEN_LIGHT = computeColor(50,   255,   50);
	public static int YELLOW = computeColor(255,   255,   0);
	
	public ColorRamp(int taillePalette) {
		this.taillePalette = taillePalette;
		this.palette = new int[taillePalette];
		this.decalageColor = 0.0;
	}
	public int getTaillePalette() {return taillePalette;}
	
	private void emptyPalette() {for (int i=0; i<taillePalette; i++) palette[i] = -1;}
	private void checkPalette() {for (int i=0; i<taillePalette; i++) if (palette[i] == -1) assert (false);}
	
	public void setDecalageColor(double val) {if (val<0) val=0; if (val>1) val=1; this.decalageColor = val;}
	public void setFactorColor(double f) {this.factorColor = f;}
	
	public void fillRougeVertJaune() {
		fillWith3Colors(255, 50, 50, 50, 255, 50, 255, 255, 50); // rouge -> vert -> jaune
	}
	public void fillMultiColor() {
		int[] tabColor = new int[7];
		tabColor[0] = RED_LIGHT;
		tabColor[1] = WHITE;
		tabColor[2] = BLUE_LIGHT;
		tabColor[3] = GRAY;
		tabColor[4] = GREEN_LIGHT;
		tabColor[5] = BLACK;
		tabColor[6] = YELLOW;
		fillWithTabColors(tabColor);
	}
	
	public void fillWithGrays() {
		emptyPalette();
		morph2Colors(0, taillePalette/2, 255,255,255, 0,0,0);
		morph2Colors(taillePalette/2, taillePalette-1, 0,0,0, 255,255,255);
		checkPalette();
	}
	public void fillWithGrays_inverted() {
		emptyPalette();
		morph2Colors(0, taillePalette/2, 0,0,0, 255,255,255);
		morph2Colors(taillePalette/2, taillePalette-1, 255,255,255, 0,0,0);
		checkPalette();
	}
	
	// Pour assurer une cyclicité, la palette contiendra :
	//   couleur1 -> couleur2 -> couleur1
	public void fillWith2Colors(int r1, int g1, int b1, int r2, int g2, int b2) {
		emptyPalette();
		morph2Colors(0, taillePalette/2, r1,g1,b1, r2,g2,b2);
		morph2Colors(taillePalette/2, taillePalette-1, r2,g2,b2, r1,g1,b1);
		checkPalette();
	}
	
	// Pour assurer une cyclicité, la palette contiendra :
	//   couleur1 -> couleur2 -> couleur3 ->couleur1
	public void fillWith3Colors(int r1, int g1, int b1, int r2, int g2, int b2, int r3, int g3, int b3) {
		emptyPalette();
		morph2Colors(0, taillePalette/3, r1,g1,b1, r2,g2,b2);
		morph2Colors(taillePalette/3, (2*taillePalette)/3, r2,g2,b2, r3,g3,b3);
		morph2Colors((2*taillePalette)/3, taillePalette-1, r3,g3,b3, r1,g1,b1);
		checkPalette();
	}
	// Pour assurer une cyclicité, la palette rebouclera sur la 1ere couleur
	public void fillWithTabColors(int[] tabColor) {
		emptyPalette();
		int nbStep = tabColor.length;
		for (int i=0; i<nbStep-1; i++) {
			int col1 = tabColor[i];
			int col2 = tabColor[i+1];
			morph2Colors((taillePalette*i)/nbStep, (taillePalette*(i+1))/nbStep, col1, col2);
		}
		// Petit rebouclage pour faire une cyclicité
		morph2Colors((taillePalette*(nbStep-1))/nbStep, taillePalette-1, tabColor[nbStep-1], tabColor[0]);
		checkPalette();
	}	

	private void morph2Colors(int indexDeb, int indexFin, int col1, int col2) {
		morph2Colors(indexDeb, indexFin, (col1 >> 16) & 0xFF, (col1 >> 8) & 0xFF, (col1 >> 0) & 0xFF,
										 (col2 >> 16) & 0xFF, (col2 >> 8) & 0xFF, (col2 >> 0) & 0xFF);
	}
	private void morph2Colors(int indexDeb, int indexFin, int r1, int g1, int b1, int r2, int g2, int b2) {
		assert(indexDeb>=0 && indexFin<taillePalette && indexFin>=indexDeb);
		int nbIter = indexFin-indexDeb+1;
		for (int i=0; i<nbIter; i++) {
			double r = (double)r1 + (double)i/nbIter*(r2-r1);
			double g = (double)g1 + (double)i/nbIter*(g2-g1);
			double b = (double)b1 + (double)i/nbIter*(b2-b1);
			palette[indexDeb + i] = computeColor((int)r, (int)g, (int)b);
		}
	}

	public int getColor(double val) {
		return getColor_DE(val);
	}
	public int getColor_DE(double de) {
		if (Double.isNaN(de))
			return RED; // pixel non calculé
		if (de<=0)
			return BLACK; // Dans l'ensemble => noir

		double k = taillePalette / Math.log(100000);
		double index = 0 - k * Math.log(de);
		
		int indexPalette = (int) ((index*factorColor)%taillePalette);
		indexPalette += (int) (decalageColor * taillePalette);
		
		while (indexPalette>=taillePalette) indexPalette-=taillePalette;
		while (indexPalette<0) indexPalette+=taillePalette;
		
		return palette[indexPalette];
	}
	public int getColor_iter(double nbIter) {
		if (Double.isNaN(nbIter))
			return RED; // pixel non calculé
		if (Double.isInfinite(nbIter))
			return BLACK; // Dans l'ensemble => noir
		
		int indexPalette = (int) ((nbIter*factorColor)%taillePalette);
		indexPalette += (int) (decalageColor * taillePalette);
		if (indexPalette>=taillePalette) indexPalette-=taillePalette;
			
		return palette[indexPalette];
	}
	
//	public int getColor_stretch(double nbIter, double NB_ITER_MAX) {
//		assert(nbIter==Double.POSITIVE_INFINITY || nbIter<=NB_ITER_MAX);
//		if (nbIter == Double.POSITIVE_INFINITY)
//			return 0; // noir
//		int index = (int)(nbIter/NB_ITER_MAX * taillePalette);
//		if (index>=taillePalette)
//			index = taillePalette;
//		return palette[index];
//	}
	
	public static int getMoyenneColor(int c1, int c2, int c3) {
		int r1 = (c1 >> 16) & 0xFF;
		int g1 = (c1 >> 8) & 0xFF;
		int b1 = (c1 >> 0) & 0xFF;
		
		int r2 = (c2 >> 16) & 0xFF;
		int g2 = (c2 >> 8) & 0xFF;
		int b2 = (c2 >> 0) & 0xFF;
		
		int r3 = (c3 >> 16) & 0xFF;
		int g3 = (c3 >> 8) & 0xFF;
		int b3 = (c3 >> 0) & 0xFF;
		
		int r =  ((r1+r2+r3)/3);
		int g =  ((g1+g2+g3)/3);
		int b =  ((b1+b2+b3)/3);
		return computeColor(r, g, b);
	}
	
	public static int getMoyenneColor(int c1, int c2, int c3, int c4) {
		int r1 = (c1 >> 16) & 0xFF;
		int g1 = (c1 >> 8) & 0xFF;
		int b1 = (c1 >> 0) & 0xFF;
		
		int r2 = (c2 >> 16) & 0xFF;
		int g2 = (c2 >> 8) & 0xFF;
		int b2 = (c2 >> 0) & 0xFF;
		
		int r3 = (c3 >> 16) & 0xFF;
		int g3 = (c3 >> 8) & 0xFF;
		int b3 = (c3 >> 0) & 0xFF;
		
		int r4 = (c4 >> 16) & 0xFF;
		int g4 = (c4 >> 8) & 0xFF;
		int b4 = (c4 >> 0) & 0xFF;
		
//		int r =  ((r1+r2+r3+r4)/4);
//		int g =  ((g1+g2+g3+g4)/4);
//		int b =  ((b1+b2+b3+b4)/4);
//		return computeColor(r, g, b);
		
		// version legerement plus optimisée
		int r =  r1+r2+r3+r4;
		int g =  g1+g2+g3+g4;
		int b =  b1+b2+b3+b4;
		r=r>>2;
		r=r<<16;
		g=g>>2;
		g=g<<8;
		b=b>>2;
		return (r | g | b);
	}

	public static int getDeltaColor(int c1, int c2) {
		int r1 = (c1 >> 16) & 0xFF;
		int g1 = (c1 >> 8) & 0xFF;
		int b1 = (c1 >> 0) & 0xFF;
		
		int r2 = (c2 >> 16) & 0xFF;
		int g2 = (c2 >> 8) & 0xFF;
		int b2 = (c2 >> 0) & 0xFF;
		
		return Math.abs(r2-r1) + Math.abs(g2-g1) + Math.abs(b2-b1);
	}
		
	
	
	private static int computeColor (int r, int g, int b) {
		//assert (r>=0 && g>=0 && b>=0 && r<=255 && g<=255 && b<=255);
		return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0);
	}
	
	private static int computeColor (double r, double g, double b) {
		//assert(r>=0.0 && g>=0.0 && b>=0.0 && r<=1.0 && g<=1.0 && b<=1.0);
		if (r==1) r=0.999999;
		if (g==1) g=0.999999;
		if (b==1) b=0.999999;
		return computeColor((int)(r*256), (int)(g*256), (int)(b*256));
	}



	
	
}
